package lib.components.scroller {
	import com.greensock.BlitMask;
	import com.greensock.TweenLite;
	import com.greensock.TweenMax;
	import com.greensock.easing.*;
	import com.greensock.plugins.ThrowPropsPlugin;
	import com.greensock.plugins.TweenPlugin;
	//import com.hiddenlevel.utils.StageLog;
	
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.utils.getTimer;
	
	TweenPlugin.activate([ThrowPropsPlugin]);	
	
	public class OmniScroll extends Sprite {
		public static const HORIZONTAL:String = "horizontal";
		public static const VERTICAL:String = "vertical";
		public static const OMNI:String = "omni";
		
		protected var box:Sprite;
		protected var vBar:Sprite;
		protected var hBar:Sprite;
		protected var boxMask:BlitMask;
		protected var bounds:Rectangle;
		protected var boxHolder:Sprite;
		protected var barHolder:Sprite;
		
		protected var downX:Number;
		protected var downY:Number;
		protected var mouseDownX:Number;
		protected var mouseDownY:Number;
		protected var mouseDeltaX:Number;
		protected var mouseDeltaY:Number;
		protected var _contentY:Number = 0;
		protected var _contentX:Number = 0;
		protected var minScrollY:Number = 0;
		protected var maxScrollY:Number = 0;
		protected var minScrollX:Number = 0;
		protected var maxScrollX:Number = 0;
		
		protected var t1:uint;
		protected var t2:uint;
		protected var x1:Number;
		protected var x2:Number;
		protected var y1:Number;
		protected var y2:Number;
		protected var barWeight:Number = 5;
		protected var barRadius:Number = 6;
		protected var barSpd:Number = 0.25;
		protected var paddingY:Number = 0;
		protected var paddingX:Number = 0;
		protected var itemW:Number = -1;
		protected var itemH:Number = -1;
		protected var currentIndex:int = -1;
		protected var currentScrollX:Number = 0;
		protected var currentScrollY:Number = 0;
		
		protected var init:Boolean = true;
		protected var active:Boolean = false;
		protected var hasDirection:Boolean = false;
		
		protected var direction:String;
		protected var mode:String;
		
		protected var scrollMode:Object;
		
		public var items:Array;
		
		public var autoSort:String = null;
		
		public var resetOnSort:Boolean = false;
		public var itemMode:Boolean = false;
		public var neverBitmap:Boolean = false;
		
		public var autoSpd:Number = 1;
		public var maxSpd:Number = 10;
		public var minSpd:Number = .25;
		public var swipeSpd:Number = 0;
		
		public var eventDown:String = MouseEvent.MOUSE_DOWN;
		public var eventMove:String = Event.ENTER_FRAME//MouseEvent.MOUSE_MOVE;
		public var eventUp:String = MouseEvent.MOUSE_UP;
		public var touchMode:Boolean = false;
		
		public function OmniScroll(w:Number, h:Number, mode:String="vertical", paddingX:Number=0, paddingY:Number=0) {						
			bounds = new Rectangle(0, 0, w, h);
			this.mode = mode;
			//StageLog.getInstance();
			
			scrollMode = {};
			scrollMode.vertical = scrollMode.horizontal = scrollMode.omni = false;
			if(mode == VERTICAL) {
				scrollMode.vertical = true;
			}
			else if(mode == HORIZONTAL) {
				scrollMode.horizontal = true;
			}
			else if(mode == OMNI) {
				scrollMode.vertical = scrollMode.horizontal = scrollMode.omni = true;
				hasDirection = true;
			}
			
			this.paddingX = paddingX;
			this.paddingY = paddingY;
			
			box = new Sprite();
			boxMask = new BlitMask(box, 0, 0, width, height, false, false);//, 0x9900FF00);
			boxMask.bitmapMode = false;
			boxHolder = new Sprite();
			vBar = new Sprite();
			hBar = new Sprite();
			barHolder = new Sprite();
			
			draw();
			
			items = [];
			
			boxMask.mouseEnabled = boxMask.mouseChildren = false;
			addEventListener(Event.ADDED_TO_STAGE, onStage);
		}
		
		protected function draw():void {
			boxHolder.graphics.beginFill(0xFF0000, 0);
			boxHolder.graphics.drawRect(0, 0, width, height);
			boxHolder.graphics.endFill();
			
			vBar.x = width - barWeight;
			vBar.alpha = 0;
			vBar.mouseEnabled = vBar.mouseChildren = false;
			
			hBar.y = height - barWeight;
			hBar.alpha = 0;
			hBar.mouseEnabled = hBar.mouseChildren = false;
			
			contentX = paddingX;
			contentY = paddingY;
			//bitmapMode = false;
			
			barHolder.addChild(vBar);
			barHolder.addChild(hBar);
			boxHolder.addChild(box);
			boxHolder.addChild(boxMask);
			addChild(boxHolder);
			addChild(barHolder);
		}
		
		protected function onStage(e:Event):void {
			removeEventListener(Event.ADDED_TO_STAGE, onStage);
			if(active) {
				boxHolder.addEventListener(eventDown, onMouseDown);
			}
		}
		
		protected function showBars():void {
			showVBar();
			showHBar();
		}
		
		protected function hideBars():void {
			hideVBar();
			hideHBar();
		}
		
		protected function showVBar():void {
			TweenMax.to(vBar, barSpd, {alpha:1});		
		}
		
		protected function hideVBar():void {
			TweenMax.to(vBar, barSpd, {alpha:0});		
		}
		
		protected function showHBar():void {
			TweenMax.to(hBar, barSpd, {alpha:1});		
		}
		
		protected function hideHBar():void {
			TweenMax.to(hBar, barSpd, {alpha:0});		
		}
		
		protected function onMouseDown(e:*):void {
			TweenMax.killTweensOf(disableBitmapMode);
			stopScroll();
			bitmapMode = false;
			
			if(!scrollMode.omni) {
				hasDirection = false;	
				direction = "";
			}
			
			x1 = x2 = contentX;
			y1 = y2 = contentY;
			t1 = t2 = getTimer();
			
			downX = contentX;
			downY = contentY;
			if(touchMode) {
				mouseDownX = e.x;
				mouseDownY = e.y;
			}
			else {
				mouseDownX = mouseX;
				mouseDownY = mouseY;	
			}
			mouseDeltaX = 0;
			mouseDeltaY = 0;
			
			addEventListener(eventMove, onMouseMove);
			stage.addEventListener(eventUp, onMouseUp);
		}
		
		protected function onMouseMove(e:*):void {
			//track velocity using the last 2 frames for more accuracy
			t2 = t1;
			t1 = getTimer();
			
			var mY:Number;
			var mX:Number;
			if(touchMode) {
				mX = e.x;
				mY = e.y;
			}
			else {
				mX = mouseX;
				mY = mouseY;	
			}
			
			checkScroll();
			
			x2 = x1;
			x1 = contentX;
			mouseDeltaX = mX - mouseDownX;
			
			y2 = y1;
			y1 = contentY;
			mouseDeltaY = mY - mouseDownY;
			
			if(!hasDirection) {
				if(mouseDeltaX != mouseDeltaY) {
					hasDirection = true;
					direction = Math.abs(mouseDeltaX) > Math.abs(mouseDeltaY) ? HORIZONTAL : VERTICAL;
					if(!scrollMode.omni && direction != mode) {
						stage.removeEventListener(eventUp, onMouseUp);
						removeEventListener(eventMove, onMouseMove);
						return;
					}
				}
			}
			
			if(hasDirection) {
				if(scrollMode.vertical) {
					if(box.height > height) {	
						followY();
					}
				}
				if(scrollMode.horizontal) {
					if(box.width > width) {
						followX();
					}
				}
			}
		}
		
		protected function checkScroll():void {
			if(scrollMode.omni) {
				if(mouseDeltaY != 0 && mouseDeltaX !=0 && !bitmapMode) {
					updateMask();
					bitmapMode = true;
					showBars();
				}
			}
			else if(scrollMode.vertical) {
				if(mouseDeltaY != 0 && !bitmapMode) {
					updateMask();
					bitmapMode = true;
					showBars();
				}
			}
			else if(scrollMode.horizontal) {
				if(mouseDeltaX !=0 && !bitmapMode) {
					updateMask();
					bitmapMode = true;
					showBars();
				}
			}
		}
		
		protected function followX():void {
			this.contentX = downX + mouseDeltaX;
		}
		
		protected function followY():void {
			this.contentY = downY + mouseDeltaY;
		}
		
		protected function onMouseUp(e:*= null):void {
			stage.removeEventListener(eventUp, onMouseUp);
			removeEventListener(eventMove, onMouseMove);
			
			if(scrollMode.omni || direction == mode) {
				
				var time:Number = (getTimer() - t2) / 1000;
				
				var durationX:Number = 0;
				var durationY:Number = 0;
				
				if(scrollMode.vertical) {
					if(box.height > height) {
						durationY = flickY(time).duration;
					}
				}
				if(scrollMode.horizontal) {
					if(box.width > width) {
						swipeSpd = durationX = flickX(time).duration;
					}
				}
				var duration:Number = Math.max(durationX, durationY);
				if(duration > 0) {
					TweenMax.delayedCall(duration, disableBitmapMode);
				}
			}
		}
		
		protected function flickX(time:Number):TweenLite {
			var tween:TweenLite;
			if(mouseDeltaX != 0 || contentX > maxScrollX || contentX < minScrollX) {
				var xVelocity:Number = (contentX - x2) / time;
				var throwMax:Number = maxScrollX;
				var throwMin:Number = minScrollX-paddingX;
				if(itemMode) {
					if(Math.abs(mouseDeltaX) < itemWidth/4) {
						throwMax = throwMin = currentScrollX;
					}
					else {
						if(mouseDeltaX < 0) {
							throwMax = throwMin = currentScrollX = currentScrollX - itemWidth >= minScrollX ? currentScrollX - itemWidth : minScrollX;
						}
						else {
							throwMax = throwMin = currentScrollX = currentScrollX + itemWidth <= maxScrollX ? currentScrollX + itemWidth : maxScrollX;
						}
					}
				}
				
				tween = ThrowPropsPlugin.to(this, {ease:Strong.easeOut, onComplete:stopScrollX,
					throwProps:{
						contentX:{
							velocity:xVelocity, max:throwMax, min:throwMin, resistance:200
						}
					}
				}, maxSpd, minSpd, 1);
				
				var index:int = Math.round(Math.abs(currentScrollX)/itemWidth);
				//trace(currentScrollX, itemWidth, index, minScrollX);
				items[index].dispatchEvent(new Event(Event.SELECT));
				
				return tween;
			}
			return new TweenLite({nothing:0}, 0, {nothing:0});
		}
		
		protected function flickY(time:Number):TweenLite {
			if(mouseDeltaY != 0 || contentY > maxScrollY || contentY < minScrollY) {				
				var yVelocity:Number = (contentY - y2) / time;
				
				return ThrowPropsPlugin.to(this, {ease:Strong.easeOut, onComplete:stopScrollY,
					throwProps:{
						contentY:{
							velocity:yVelocity, max:maxScrollY, min:minScrollY-paddingY, resistance:200
						}
					}
				}, maxSpd, minSpd, 1);
			}
			return new TweenLite({nothing:0}, 0, {nothing:0});
		}
		
		protected function disableBitmapMode():void {
			bitmapMode = false;
		}
		
		protected function enableBitmapMode():void {
			bitmapMode = true;
		}
		
		public function get bitmapMode():Boolean {
			return boxMask.bitmapMode;
		}
		
		public function set bitmapMode(value:Boolean):void {
			if(!neverBitmap)
				boxMask.bitmapMode = value;
			//trace(bitmapMode ? "interaction off" : "interaction on");
		}
		
		protected function itemChanged(e:Event):void {
			
		}
		
		protected function itemSelected(e:Event):void {
			//trace("EVENT", currentIndex, items.indexOf(e.currentTarget), e.target, e.currentTarget);
			current = items.indexOf(e.currentTarget);
		}
		
		protected function positionItem(item:DisplayObject, n:int=-1):void {
			n = n < 0 ? items.length : n;
			
			item.x = paddingX;
			item.y = paddingY;
			if(itemHeight > 0 && scrollMode.vertical) {
				item.y += n * itemHeight;
			}
				
			else if(itemWidth > 0 && scrollMode.horizontal) {
				item.x += n * itemWidth;
			}
		}
		
		protected function positionItems():void {
			for(var i:int = 0; i < items.length; i++) {
				positionItem(items[i], i);
			}
		}
		
		protected function shiftItems(n:int, removing:Boolean=false, item:DisplayObject=null):void {
			var sign:int = removing ? -1 : 1;
			if(itemHeight > 0 && scrollMode.vertical) {
				while(n < items.length) {
					items[n].y += sign*itemHeight;
					n++;
				}
			}
				
			else if(itemWidth > 0 && scrollMode.horizontal) {
				while(n < items.length) {
					items[n].x += sign*itemWidth;
					n++;
				}
			}
		}
		
		protected function clearItem(item:DisplayObject, n:int, autoUpdate:Boolean=true):void {
			item.removeEventListener(Event.SELECT, itemSelected);
			item.removeEventListener(Event.CHANGE, itemChanged);
			Object(item).turnOff();
			shiftItems(n, true, item);
			
			if(n > -1 && n < items.length) {
				items.splice(n, 1);
			}
			if(box.contains(item)) {
				box.removeChild(item);
			}
			
			if(current == n && items.length > 0) {
				current = 0;
			}
			if(items.length == 0) {
				current = -1;
			}
			
			if(autoUpdate && items.length > 0) {
				update();
			}
		}
		
		public function enableScroll():void {
			turnOn();
		}
		
		public function disableScroll():void {
			bitmapMode = false;
			TweenMax.killTweensOf(disableBitmapMode);
			stopScroll();
			turnOff();
		}
		
		public function reset():void {
			contentX = paddingX
			contentY = paddingY;
		}
		
		protected function updateMask():void {
			//if(!neverBitmap) {
			boxMask.update(null, true);
			//}
		}
		
		public function update(recaptureBitmap:Boolean=true):void {
			if(recaptureBitmap) {
				updateMask();
			}
			
			minScrollX = itemWidth > -1 ? bounds.width - (items.length*itemWidth + paddingX) : bounds.width - (box.width + paddingX);
			maxScrollX = paddingX;
			
			minScrollY = itemHeight > -1 ? bounds.height - (items.length*itemHeight + paddingY) : bounds.height - (box.height + paddingY);
			maxScrollY = paddingY;
			
			if(scrollMode.vertical) {
				vBar.graphics.clear();
				if(box.height > height) {
					vBar.graphics.beginFill(0x505050, .8);
					vBar.graphics.drawRoundRect(0, 0, barWeight, height/box.height*height, barRadius, barRadius);
					vBar.graphics.endFill();
				}
			}
			
			if(scrollMode.horizontal) {
				hBar.graphics.clear();
				if(box.width > width) {
					hBar.graphics.beginFill(0x505050, .8);
					hBar.graphics.drawRoundRect(0, 0, width/box.width*width, barWeight, barRadius, barRadius);
					hBar.graphics.endFill();
				}
			}
		}
		
		public function addItem(item:DisplayObject, updateNow:Boolean=true, selectNow:Boolean=true, sortNow:Boolean=true):void {
			//trace(item.width, item.height);
			addItemAt(item, items.length);
			
			if(sortNow && autoSort && items.length > 1) {
				sortOn(autoSort, false);
			}
			
			if(updateNow) {
				update();
			}
			
			if(selectNow && items.length == 1) {
				current = 0;
			}
		}
		
		public function addItemAt(item:DisplayObject, n:int):void {
			positionItem(item, n);
			items.splice(n, 0, item);
			box.addChildAt(item, 0);
			
			n++;
			shiftItems(n, false, item);
			
			if(active) {
				Object(item).turnOn();
				item.addEventListener(Event.SELECT, itemSelected);
				item.addEventListener(Event.CHANGE, itemChanged);
			}
		}
		
		public function removeItem(item:DisplayObject, autoUpdate:Boolean=true):void {
			clearItem(item, items.indexOf(item), autoUpdate);
		}
		
		public function removeItemAt(n:int, autoUpdate:Boolean=true):void {
			clearItem(items[n], n, autoUpdate);
		}
		
		public function removeAllItems(autoUpdate:Boolean=true):void {
			while(items.length > 0) {
				removeItemAt(0, autoUpdate);
			}
		}
		
		public function stopScroll():void {
			TweenMax.killTweensOf(this, false, {contentX:true, contentY:true});
			hideBars();
		}
		
		public function stopScrollX():void {
			TweenMax.killTweensOf(this, false, {contentX:true});
			hideHBar();
		}
		
		public function stopScrollY():void {
			TweenMax.killTweensOf(this, false, {contentY:true});
			hideVBar();
		}
		
		public function get contentX():Number {
			return _contentX;
		}
		
		public function set contentX(value:Number):void {
			if(scrollMode.horizontal) {
				_contentX = value;
				boxMask.scrollX = minScrollX == 0 ? 0 : contentX/(bounds.width - box.width);//contentX/minScrollX;
				//trace(contentX/minScrollX);
				
				var max:Number = width - hBar.width;
				var barX:Number = boxMask.scrollX*(max);
				hBar.x = Math.max(Math.min(max, barX), 0);
			}
		}
		
		public function get contentY():Number {
			return _contentY;
		}
		
		public function set contentY(value:Number):void {
			if(scrollMode.vertical) {				
				_contentY = value;
				boxMask.scrollY = minScrollY == 0 ? 0 : contentY/(bounds.height - box.height);//contentY/minScrollY;
				
				var max:Number = height - vBar.height;
				var barY:Number = boxMask.scrollY*(max);
				vBar.y = Math.max(Math.min(max, barY), 0);
			}
		}
		
		public function scrollToX(x:Number):void {
			//update();
			currentScrollX = -x;
			//bitmapMode = true;
			TweenMax.to(this, autoSpd, {contentX:-x, onComplete:disableBitmapMode});
		}
		
		public function scrollToY(y:Number):void {
			//update();
			currentScrollY = -y;
			//bitmapMode = true;
			TweenMax.to(this, autoSpd, {contentY:-y, onComplete:disableBitmapMode});
		}
		
		public function scrollToXY(x:Number, y:Number):void {
			//update();
			//bitmapMode = true;
			currentScrollX = -x;
			currentScrollY = -y;
			TweenMax.to(this, autoSpd, {contentX:-x, contentY:-y, onComplete:disableBitmapMode});
		}
		
		public function gotoXY(x:Number, y:Number):void {
			contentX = -x;
			contentY = -y;	
		}
		
		public function turnOn(listeners:Boolean=true):void {
			active = true;
			
			if(listeners) {
				addListeners();
			}
		}
		
		private function addListeners():void {
			boxHolder.addEventListener(eventDown, onMouseDown);
			
			for(var i:int = 0; i < items.length; i++) {
				items[i].addEventListener(Event.SELECT, itemSelected);
				items[i].addEventListener(Event.CHANGE, itemChanged);
				//items[i].turnOn();
			}
		}
		
		public function turnOff():void {	
			active = false;
			
			removeEventListener(Event.ADDED_TO_STAGE, onStage);
			boxHolder.removeEventListener(eventDown, onMouseDown);
			removeEventListener(eventMove, onMouseMove);
			
			if(stage) {
				stage.removeEventListener(eventUp, onMouseUp);
			}
			for(var i:int = 0; i < items.length; i++) {
				items[i].removeEventListener(Event.SELECT, itemSelected);
				items[i].removeEventListener(Event.CHANGE, itemChanged);
				items[i].turnOff();
			}
		}
		
		public function get bars():Boolean {
			return vBar.visible;
		}
		
		public function set bars(value:Boolean):void {
			vBar.visible = hBar.visible = value;
		}
		
		public override function get width():Number {
			return bounds.width;
		}
		
		public override function set width(value:Number):void {
			boxMask.width = bounds.width = value;
			update(false);
		}
		
		public override function get height():Number {
			return bounds.height;
		}
		
		public override function set height(value:Number):void {
			boxMask.height = bounds.height = value;
			update(false);
		}
		
		public function get itemWidth():Number {
			return itemW;
		}
		
		public function set itemWidth(value:Number):void {
			itemW = value;
		}
		
		public function get itemHeight():Number {
			return itemH;
		}
		
		public function set itemHeight(value:Number):void {
			itemH = value;
		}
		
		public function get current():int {
			return currentIndex;
		}
		
		public function set current(value:int):void {
			if(value != current) {
				if(currentIndex > -1) {
					if(currentItem && currentItem.hasOwnProperty("deselect")) {
						Object(currentItem).deselect();
					}
				}
				currentIndex = value;
				if(currentIndex != -1) {
					//trace("CHANGE", currentIndex, value, items[value]);
					if(currentItem.hasOwnProperty("select")) {
						Object(currentItem).select(null, false);	
					}
					dispatchEvent(new Event(Event.CHANGE));
				}
			}
				
			else if(value != -1) {
				//trace("SELECT", currentIndex, value, currentItem);
				dispatchEvent(new Event(Event.SELECT));
			}
		}
		
		public function get currentItem():DisplayObject {
			if(current > -1) {
				return items[current];
			}
			return null;
		}
		
		public function get currentId():String {
			if(currentItem) {
				return Object(currentItem).id;
			}
			return null;
		}
		
		/*public function get touchMode():Boolean {
			return eventDown == CynergieTouchEvent.TWO_DOWN ? true : false;
		}
		
		public function set touchMode(value:Boolean):void {
			if(value) {
				eventDown = CynergieTouchEvent.TWO_DOWN;
				//eventMove = CynergieTouchEvent.TWO_MOVE;
				eventUp = CynergieTouchEvent.TWO_END;
			}
			else {
				eventDown = MouseEvent.MOUSE_DOWN;
				//eventMove = MouseEvent.MOUSE_MOVE;
				eventUp = MouseEvent.MOUSE_UP;
			}
		}*/
		
		public function setSize(width:Number, height:Number):void {
			boxMask.setSize(width, height);
			bounds.width = width;
			bounds.height = height;
						
			update(false);
		}
		
		public function sortOn(key:String, resetNow:Boolean=false):void {
			var item:DisplayObject = currentItem;
			items.sortOn(key, Array.CASEINSENSITIVE);
			
			positionItems();
			
			currentIndex = items.indexOf(item);
			if(resetOnSort && resetNow) {
				if(current != 0) {
					current = 0;
				}
			}
		}
		
		public function refresh():void {
			positionItems();
			update();
		}
	}
}